package org.xbl.xchain.sdk.amino;

public class VarInt {

    public static byte[] putVarint(long value) {
        return putUvarint((value << 1) ^ (value >> 31));
    }

    public static byte[] putUvarint(long value) {
        byte[] buf = new byte[10];
        int i = 0;
        while ((value & 0xFFFFFF80) != 0L) {
            buf[i++] = ((byte) ((value & 0x7F) | 0x80));
            value >>>= 7;
        }
        buf[i] = ((byte) (value & 0x7F));
        byte[] out = new byte[i + 1];
        for (; i >= 0; i--) {
            out[i] = buf[i];
        }
        return out;
    }

    public static CountN readUVarInt(byte[] bytes) {
        int value = 0;
        int s = 0;
        byte rb = Byte.MIN_VALUE;
        int i = 0;
        for (; i < bytes.length; i++) {
            byte b = bytes[i];
            rb = b;
            if ((b & 0x80) == 0) {
                break;
            }
            value |= (b & 0x7f) << s;
            s += 7;
            if (s > 35) {
                throw new IllegalArgumentException("Variable length quantity is too long");
            }
        }
        value = value | (rb << s);
        return new CountN(value, i + 1);
    }

    public static CountN readVarInt(byte[] bytes) {
        CountN countN = readUVarInt(bytes);
        int raw = countN.getNum();
        // This undoes the trick in writeSignedVarInt()
        int temp = (((raw << 31) >> 31) ^ raw) >> 1;
        // This extra step lets us deal with the largest signed values by treating
        // negative results from read unsigned methods as like unsigned values.
        // Must re-flip the top bit if the original read value had it set.
        int count = temp ^ (raw & (1 << 31));
        countN.setNum(count);
        return countN;
    }

    public static class CountN {
        private Integer num;
        private Integer offset;

        public CountN(Integer num, Integer offset) {
            this.num = num;
            this.offset = offset;
        }

        public void setNum(Integer num) {
            this.num = num;
        }

        public void setOffset(Integer offset) {
            this.offset = offset;
        }

        public Integer getNum() {
            return num;
        }

        public Integer getOffset() {
            return offset;
        }
    }
}
